﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Qadir.Collections.Generic;

namespace Qadir.MVC.Streamline
{
    /// <summary>
    /// The bread and butter of the Qadir Framework - a rich, flowing object allowing for sequential execution of your program.
    /// </summary>
    public class Streamline : IModel
    {
        /// <summary>
        /// All routes to views within this Streamline model.
        /// </summary>
        public Trictionary<string, Type, MethodInfo> Routes { get; set; }

        /// <summary>
        /// All controllers within this Streamline model.
        /// </summary>
        public Manager<IController> Controllers { get; set; }

        /// <summary>
        /// All timelines within this Streamline model.
        /// </summary>
        public Manager<Timeline> Timelines { get; set; }

        /// <summary>
        /// Adds a view to the view pool.
        /// </summary>
        /// <param name="view">The type of the view to be added.</param>
        /// <exception cref="Qadir.MVC.Streamline.ViewException">Throws a ViewException if the provided type is not fit to be a View.</exception>
        public void AddView(Type view)
        {
            Manager<object> attributes = new Manager<object>(view.GetCustomAttributes(typeof(ViewAttribute), true));
            if (attributes.Count > 0)
            {
                Manager<ViewAttribute> viewAttrs = attributes.ApplyActionToAllAndReceiveResults<ViewAttribute>((attr) => { return (ViewAttribute)attr; });
                Manager<MethodInfo> suitableMethods = (new Manager<MethodInfo>(view.GetMethods())).ApplyActionToAllAndReceiveResults<MethodInfo>((method) =>
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    return (method.ReturnType == typeof(void) && parameters.Length == 2 && parameters[0].ParameterType.IsAssignableFrom(typeof(IModel)) && parameters[1].ParameterType.IsAssignableFrom(typeof(Dictionary<string, object>))) ? method : null;
                }).GetElementsWhere((method) => { return method != null; });
                if (suitableMethods.Count > 0)
                {
                    foreach (ViewAttribute viewAttr in viewAttrs)
                    {
                        foreach (MethodInfo make in suitableMethods)
                        {
                            Routes.Add(viewAttr.Name, view, make);
                        }
                    }
                }
                else
                    throw new ViewException("Provided type does not contain any methods assignable to Qadir.MVC.Streamline.MakeView.");
            }
            else
                throw new ViewException("Provided type does not contain any ViewAttribute objects.");
        }

        /// <summary>
        /// Adds a set of views to the view pool.
        /// </summary>
        /// <param name="views">The types of the views to be added.</param>
        public void AddViewRange(IEnumerable<Type> views)
        {
            foreach (Type view in views)
            {
                AddView(view);
            }
        }

        /// <summary>
        /// Executes the actions of all timelines within this Streamline model.
        /// </summary>
        /// <param name="arguments">Any arguments to pass to the timelines.</param>
        public void RunAllTimelines(object[] arguments = null)
        {
            RunTimelines(Timelines, arguments);
        }

        /// <summary>
        /// Executes all actions on the specified timeline.
        /// </summary>
        /// <param name="timeline">The timeline to run.</param>
        /// <param name="arguments">Any arguments to pass to the timeline.</param>
        /// <returns>Returns the end code of the timeline.</returns>
        public int RunTimeline(Timeline timeline, object[] arguments = null)
        {
            return timeline(this, arguments);
        }

        /// <summary>
        /// Executes all actions on the specified timelines.
        /// </summary>
        /// <param name="timeline">The timelines to run.</param>
        /// <param name="arguments">Any arguments to pass to the timeline.</param>
        public void RunTimelines(System.Collections.Generic.IEnumerable<Timeline> timelines, object[] arguments = null)
        {
            foreach (Timeline timeline in timelines)
            {
                timeline(this, arguments);
            }
        }

        /// <summary>
        /// Queries the view pool and shows the desired view.
        /// </summary>
        /// <param name="name">The name of the view to be shown.</param>
        /// <param name="args">Any arguments to be passed to the view.</param>
        public void ShowView(string name, Dictionary<string, object> args)
        {
            if (Routes.ContainsKey(name))
            {
                KeyValuePair<Type, MethodInfo> route = Routes[name];
                if (route.Key.IsAbstract && route.Key.IsSealed)
                {
                    route.Value.Invoke(null, new object[] { this, args });
                }
                else
                {
                    object instance = Activator.CreateInstance(route.Key);
                    route.Value.Invoke(instance, new object[] { this, args });
                }
            }
            else
                throw new RoutingException(string.Format("Unknown route \"{0}\".", name));
        }

        public Streamline()
        {
            Routes = new Trictionary<string, Type, MethodInfo>();
            Controllers = new Manager<IController>();
        }

        /// <summary>
        /// The entry point of the streamline. Works best when hooked up to the application's Main method.
        /// </summary>
        /// <param name="arguments">Any and all arguments passed to the streamline.</param>
        /// <param name="streamLine">A parent Streamline that will call execution of this one.</param>
        /// <returns>Returns a System.Int32 reflecting the success or failure of the operation.</returns>
        public virtual int StreamlineMain(object[] arguments = null)
        {
            return 0;
        }
    }
}
